// PART OF THE MACHINE SIMULATION. DO NOT CHANGE.

package nachos.machine;

import nachos.security.*;
import nachos.threads.KThread;

import java.util.Vector;
import java.security.PrivilegedAction;

/**
 * TCB 模拟创建、上下文切换和销毁 Nachos 线程所需的低级细节
 * 每个 TCB 控制一个底层 JVM 线程对象
 * <p>
 * 不要使用 java.lang.Thread 中的任何方法，因为它们与 TCB API 不兼容
 * 大多数 Thread 方法要么使 Nachos 崩溃，要么没用.
 * <p>
 * 不要在代码的任何地方使用 synchronized 关键字
 * 这样违背规则，而且很容易使 nachos 陷入死锁
 */
public final class TCB {
    /**
     * 声明一个新的 TCB
     */
    public TCB() {
    }

    /**
     * 给 TCB 类必要的特权来创建线程
     * 这是必要的，因为与其他需要特权的机器类不同，我们希望内核能够自己创建 TCB 对象
     *
     * @param privilege 封装对 Nachos 机器的特权访问
     */
    public static void givePrivilege(Privilege privilege) {
        TCB.privilege = privilege;
        privilege.tcb = new TCBPrivilege();
    }

    /**
     * 启动 TCB，使此 TCB 表示的线程开始执行，指定的目标在线程中运行
     */
    public void start(Runnable target) {

        // 我们不会在这里使用同步
        // 我们假设这是对 start() 的第一次调用，并且我们在另一个 TCB 的上下文中被调用
        // 因为我们一次只允许运行一个TCB，所以不需要同步。
        // 打破这一假设的唯一方式是，我们的一个非 Nachos 线程使用了 TCB 代码

        // 确保此 TCB 尚未启动
        // 如果 done 为 false，则 destroy() 尚未将 javaThread 设置回 null
        // 因此，我们可以使用 javaThread 作为检测 start() 是否被调用的可靠检测器
        Lib.assertTrue(javaThread == null && !done);

        // 确保没有太多正在运行的 TCB
        // 存在此限制是为了防止使用野生线程
        Lib.assertTrue(runningThreads.size() < maxThreads);

        isFirstTCB = (currentTCB == null);

        // 可能不必要但是有道理的检测：
        // 如果这不是第一个TCB，我们将确保当前线程绑定到当前TCB
        // 只有当非 Nachos 线程调用 start() 时，该检测才会失败
        if (!isFirstTCB)
            Lib.assertTrue(currentTCB.javaThread == Thread.currentThread());

        // 此时，所有的检测都已经完成，因此我们可以继续启动 TCB
        // 无论这是否是第一个 TCB，它都会被添加到 runningThreads 容器中，我们保存目标闭包
        runningThreads.add(this);

        this.target = target;

        // 无论是否是第一个 TCB，最终都是执行 threadroot() 函数
        // threadroot() 函数最终会执行 target.run()

        if (!isFirstTCB) {
            // 如果这不是第一个 TCB，我们必须创建一个新的 Java 线程来运行它
            // 创建 Java 线程是一项特权操作
            tcbTarget = new Runnable() {
                public void run() {
                    threadroot();
                }
            };

            privilege.doPrivileged(new Runnable() {
                public void run() {
                    javaThread = new Thread(tcbTarget);
                }
            });

            // Java 线程尚未启动，但我们需要在 yield() 中阻止它
            // 我们通过暂时关闭当前 TCB、启动新的 Java 进程、等待它将我们从 threadroot() 唤醒来实现这一点
            // 一旦新的 TCB 唤醒我们，就可以安全地切换到新的 TCB
            currentTCB.running = false;

            this.javaThread.start();
            currentTCB.waitForInterrupt();
        } else {
            // 这是第一个 TCB，所以我们不需要创建新的 Java 线程来运行它
            // 我们只是窃取了当前的 Java 线程
            javaThread = Thread.currentThread();

            // 我们现在要做的就是直接调用 threadroot()
            threadroot();
        }
    }

    /**
     * Return the TCB of the currently running thread.
     */
    public static TCB currentTCB() {
        return currentTCB;
    }

    /**
     * 在当前 TCB 与该 TCB 之间上下文切换，该 TCB 将会称为新的当前 TCB
     * 可接受此 TCB 为当前 TCB
     */
    public void contextSwitch() {
        // 可能不必要的合理性检查：
        // 我们要确保当前线程绑定到当前 TCB
        // 当且仅当在非 Nachos 线程调用 start() 时，这种检查才会失败
        Lib.assertTrue(currentTCB.javaThread == Thread.currentThread());

        // 确保 AutoGrader.runningThread() 是 associateThread()
        Lib.assertTrue(currentTCB.associated);

        currentTCB.associated = false;

        // 如果无法从 TCB 切换到自身，返回
        if (this == currentTCB)
            return;

        // 将正在执行的 TCB 陷入等待，将调用该方法的 TCB 唤醒
        // 这里有一些同步问题：
        // 一旦我们唤醒下一个线程，我们就不能对静态变量或任何 TCB 的状态进行任何假设
        // 因此在唤醒下一个线程之前，我们必须锁定 currentTCB 的值，并将它的 running 设置为 false
        // 这样的话，如果我们在调用 yield() 之前被中断，中断将设置 running，并且 yield() 不会阻塞
        TCB previous = currentTCB;
        previous.running = false;

        this.interrupt();
        previous.yield();
    }

    /**
     * Destroy this TCB. This TCB must not be in use by the current thread.
     * This TCB must also have been authorized to be destroyed by the
     * autograder.
     */
    public void destroy() {
        // make sure the current TCB is correct
        Lib.assertTrue(currentTCB != null &&
                currentTCB.javaThread == Thread.currentThread());
        // can't destroy current thread
        Lib.assertTrue(this != currentTCB);
        // thread must have started but not be destroyed yet
        Lib.assertTrue(javaThread != null && !done);

        // ensure AutoGrader.finishingCurrentThread() called authorizeDestroy()
        Lib.assertTrue(nachosThread == toBeDestroyed);
        toBeDestroyed = null;

        this.done = true;
        currentTCB.running = false;

        this.interrupt();
        currentTCB.waitForInterrupt();

        this.javaThread = null;
    }

    /**
     * Destroy all TCBs and exit Nachos. Same as <tt>Machine.terminate()</tt>.
     */
    public static void die() {
        privilege.exit(0);
    }

    /**
     * Test if the current JVM thread belongs to a Nachos TCB. The AWT event
     * dispatcher is an example of a non-Nachos thread.
     *
     * @return <tt>true</tt> if the current JVM thread is a Nachos thread.
     */
    public static boolean isNachosThread() {
        return (currentTCB != null &&
                Thread.currentThread() == currentTCB.javaThread);
    }

    private void threadroot() {
        // this should be running the current thread
        Lib.assertTrue(javaThread == Thread.currentThread());

        if (!isFirstTCB) {
            /* start() is waiting for us to wake it up, signalling that it's OK
             * to context switch to us. We leave the running flag false so that
             * we'll still run if a context switch happens before we go to
             * sleep. All we have to do is wake up the current TCB and then
             * wait to get woken up by contextSwitch() or destroy().
             */

            currentTCB.interrupt();
            this.yield();
        } else {
            /* start() called us directly, so we just need to initialize
             * a couple things.
             */

            currentTCB = this;
            running = true;
        }

        try {
            target.run();

            // no way out of here without going throw one of the catch blocks
            Lib.assertNotReached();
        } catch (ThreadDeath e) {
            // make sure this TCB is being destroyed properly
            if (!done) {
                System.out.print("\nTCB terminated improperly!\n");
                privilege.exit(1);
            }

            runningThreads.removeElement(this);
            if (runningThreads.isEmpty())
                privilege.exit(0);
        } catch (Throwable e) {
            System.out.print("\n");
            e.printStackTrace();

            runningThreads.removeElement(this);
            if (runningThreads.isEmpty())
                privilege.exit(1);
            else
                die();
        }
    }

    /**
     * 当需要等待另一个 TCB 上下文切换到此 TCB 时，被 threadroot() 和 contextSwitch() 调用
     * 因为这个 TCB 可能会被破坏，我们需要在唤醒时检查一下 done 标志，以检测它是否已经被 destroy
     * 如果设置了，则唤醒我们的 TCB 正在 destroy（） 中等待确认。
     * 否则，我们只需将 currentTCB 设置为该 TCB 并返回。
     */
    private void yield() {

        // 当前 TCB 等待唤醒
        waitForInterrupt();

        if (done) {
            // 唤醒当前 TCB
            currentTCB.interrupt();
            throw new ThreadDeath();
        }

        currentTCB = this;
    }

    /**
     * 等待绑定到此 TCB 的监视器，直到其 running 被设置为 true
     * 适用场景：TCB 需要等待轮到它运行
     * 这包括启动和销毁 TCB 的 ping-pong 过程，以及从这个 TCB 到另一个 TCB 的上下文切换
     * 我们不依赖 currentTCB，因为在我们调用它之前，它就已经被 contextSwitch() 更新了
     */
    private synchronized void waitForInterrupt() {

        while (!running) {
            try {
                // 利用了 Java 线程的 wait() 实现
                wait();
            } catch (InterruptedException e) {
            }
        }
    }

    /**
     * 通过设置 running 为 true、向绑定到它的监视器发送信号的方式唤醒这个 TCB
     * 用于启动和销毁 TCB 的 ping-pong 过程，以及切换到此 TCB 的上下文
     */
    private synchronized void interrupt() {
        running = true;

        // 利用了 Java 线程的 notify() 实现
        notify();
    }

    private void associateThread(KThread thread) {
        // make sure AutoGrader.runningThread() gets called only once per
        // context switch
        Lib.assertTrue(!associated);
        associated = true;

        Lib.assertTrue(thread != null);

        if (nachosThread != null)
            Lib.assertTrue(thread == nachosThread);
        else
            nachosThread = thread;
    }

    private static void authorizeDestroy(KThread thread) {
        // make sure AutoGrader.finishingThread() gets called only once per
        // destroy
        Lib.assertTrue(toBeDestroyed == null);
        toBeDestroyed = thread;
    }

    /**
     * The maximum number of started, non-destroyed TCB's that can be in
     * existence.
     */
    public static final int maxThreads = 250;

    /**
     * A reference to the currently running TCB. It is initialized to
     * <tt>null</tt> when the <tt>TCB</tt> class is loaded, and then the first
     * invocation of <tt>start(Runnable)</tt> assigns <tt>currentTCB</tt> a
     * reference to the first TCB. After that, only <tt>yield()</tt> can
     * change <tt>currentTCB</tt> to the current TCB, and only after
     * <tt>waitForInterrupt()</tt> returns.
     *
     * <p>
     * Note that <tt>currentTCB.javaThread</tt> will not be the current thread
     * if the current thread is not bound to a TCB (this includes the threads
     * created for the hardware simulation).
     */
    private static TCB currentTCB = null;

    /**
     * A vector containing all <i>running</i> TCB objects. It is initialized to
     * an empty vector when the <tt>TCB</tt> class is loaded. TCB objects are
     * added only in <tt>start(Runnable)</tt>, which can only be invoked once
     * on each TCB object. TCB objects are removed only in each of the
     * <tt>catch</tt> clauses of <tt>threadroot()</tt>, one of which is always
     * invoked on thread termination. The maximum number of threads in
     * <tt>runningThreads</tt> is limited to <tt>maxThreads</tt> by
     * <tt>start(Runnable)</tt>. If <tt>threadroot()</tt> drops the number of
     * TCB objects in <tt>runningThreads</tt> to zero, Nachos exits, so once
     * the first TCB is created, this vector is basically never empty.
     */
    private static Vector runningThreads = new Vector();

    private static Privilege privilege;
    private static KThread toBeDestroyed = null;

    /**
     * <tt>true</tt> if and only if this TCB is the first TCB to start, the one
     * started in <tt>Machine.main(String[])</tt>. Initialized by
     * <tt>start(Runnable)</tt>, on the basis of whether <tt>currentTCB</tt>
     * has been initialized.
     */
    private boolean isFirstTCB;

    /**
     * A reference to the Java thread bound to this TCB. It is initially
     * <tt>null</tt>, assigned to a Java thread in <tt>start(Runnable)</tt>,
     * and set to <tt>null</tt> again in <tt>destroy()</tt>.
     */
    private Thread javaThread = null;

    /**
     * <tt>true</tt> if and only if the Java thread bound to this TCB ought to
     * be running. This is an entirely different condition from membership in
     * <tt>runningThreads</tt>, which contains all TCB objects that have
     * started and have not terminated. <tt>running</tt> is only <tt>true</tt>
     * when the associated Java thread ought to run ASAP. When starting or
     * destroying a TCB, this is temporarily true for a thread other than that
     * of the current TCB.
     */
    private boolean running = false;

    /**
     * Set to <tt>true</tt> by <tt>destroy()</tt>, so that when
     * <tt>waitForInterrupt()</tt> returns in the doomed TCB, <tt>yield()</tt>
     * will know that the current TCB is doomed.
     */
    private boolean done = false;

    private KThread nachosThread = null;
    private boolean associated = false;
    private Runnable target;
    private Runnable tcbTarget;

    private static class TCBPrivilege implements Privilege.TCBPrivilege {
        public void associateThread(KThread thread) {
            Lib.assertTrue(currentTCB != null);
            currentTCB.associateThread(thread);
        }

        public void authorizeDestroy(KThread thread) {
            TCB.authorizeDestroy(thread);
        }
    }
}
